package com.sdy.mvc.feign;

import com.sdy.mvc.config.FeignConfig;
import feign.CollectionFormat;
import feign.Contract;
import feign.Feign;
import feign.MethodMetadata;
import feign.Request;
import feign.Types;
import lombok.extern.slf4j.Slf4j;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.stream.Collectors;

import static feign.Util.checkState;

/**
 * 自定义RPC合约
 * @author zhouziqiang
 */
@Slf4j
public class RpcBodyContract extends Contract.BaseContract {
    
    @Override
    protected MethodMetadata parseAndValidateMetadata(Class<?> targetType, Method method) {
        try {
            Constructor<MethodMetadata> constructor = MethodMetadata.class.getDeclaredConstructor();
            constructor.setAccessible(true);
            MethodMetadata data = constructor.newInstance();
            Method typeMethod = Types.class.getDeclaredMethod("resolve", Type.class, Class.class, Type.class);
            typeMethod.setAccessible(true);
            data.returnType((Type) typeMethod.invoke(null, targetType, targetType, method.getGenericReturnType()));
            data.configKey(Feign.configKey(targetType, method));

            if (targetType.getInterfaces().length == 1) {
                processAnnotationOnClass(data, targetType.getInterfaces()[0]);
            }
            processAnnotationOnClass(data, targetType);
            processMethod(data, method);
            checkState(data.template().method() != null,
                    "Method %s not annotated with HTTP method type (ex. GET, POST)",
                    method.getName());

            Parameter[] parameters = method.getParameters();
            int count = parameters.length;
            for (int i = 0; i < count; i++) {
                processParameter(data, parameters[i], i);
            }
            return data;
        } catch (Exception e) {
            log.error("", e);
            throw new RuntimeException("Customize parseAndValidateMetadata error!");
        }
    }

    private void processMethod(MethodMetadata data, Method method) {
        data.template().method(Request.HttpMethod.POST);
        data.template().uri(generateMappingUrl(method));
        data.template().decodeSlash(true);
        data.template().collectionFormat(CollectionFormat.EXPLODED);
        if (Arrays.stream(method.getParameters()).anyMatch(parameter -> !parameter.isNamePresent())) {
            log.error("Parameter's real name is not present! Please enable parameter information in ide settings.");
//            System.exit(-1);
        }
        Parameter[] parameters = method.getParameters();
        for (int i = 0; i < parameters.length; i++) {
            data.formParams().add(String.valueOf(i));
        }
    }

    private void processParameter(MethodMetadata data, Parameter parameter, int paramIndex) {
        String name = String.valueOf(paramIndex);
        nameParam(data, name, paramIndex);
        data.indexToEncoded().put(paramIndex, false);
    }

    @Override
    protected void processAnnotationOnClass(MethodMetadata data, Class<?> clz) {
        data.template().method(Request.HttpMethod.POST);
    }

    @Override
    protected void processAnnotationOnMethod(MethodMetadata data, Annotation methodAnnotation, Method method) {
    }

    @Override
    protected boolean processAnnotationsOnParameter(MethodMetadata data, Annotation[] annotations, int paramIndex) {
        return true;
    }
    
    public static String generateMappingUrl(Method method) {
        return "/".concat(FeignConfig.RPC_URL_PREFIX).concat("/")
                .concat(method.getDeclaringClass().getCanonicalName())
                .concat("..").concat(method.getName())
                .concat("..").concat(Arrays.stream(method.getParameterTypes())
                        .map(Class::getCanonicalName)
                        .collect(Collectors.joining(",")));
    }
}
